package comp;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import dpdetector.util.RelationJudgeUtil;
import dpdetector.util.RelationRetrivalUtil;

import entity.Metrics;
import recoder.CrossReferenceServiceConfiguration;
import recoder.ParserException;
import recoder.abstraction.ClassType;
import recoder.abstraction.Constructor;
import recoder.abstraction.Method;
import recoder.abstraction.ProgramModelElement;
import recoder.abstraction.Type;
import recoder.convenience.ForestWalker;
import recoder.convenience.TreeWalker;
import recoder.io.DefaultProjectFileIO;
import recoder.io.SourceFileRepository;
import recoder.java.CompilationUnit;
import recoder.java.ProgramElement;
import recoder.java.declaration.ClassDeclaration;
import recoder.java.declaration.Extends;
import recoder.java.declaration.FieldSpecification;
import recoder.java.declaration.Implements;
import recoder.java.declaration.InterfaceDeclaration;
import recoder.java.declaration.TypeDeclaration;
import recoder.java.reference.MethodReference;
import recoder.java.reference.TypeReference;
import recoder.service.SourceInfo;


public class ModelBuilder {
	private String prjFilePath;
	private Map<String,Metrics> metricsMap;
	private SourceInfo si;
	private List<CompilationUnit> cul;
	RelationRetrivalUtil rru ;
	
	public ModelBuilder(String prjFilePath){
		this.prjFilePath = prjFilePath;
		metricsMap = new HashMap<String,Metrics>();
	}
	
	private void init() throws IOException, ParserException{
		
		File  prjFile= new File(prjFilePath);
		CrossReferenceServiceConfiguration crsc = new CrossReferenceServiceConfiguration();
		String[] files = new DefaultProjectFileIO(crsc,prjFile).load();
    	SourceFileRepository sfr = crsc.getSourceFileRepository();
    	crsc.getProjectSettings().ensureExtensionClassesAreInPath();
    	cul = sfr.getAllCompilationUnitsFromPath();
    	si = crsc.getSourceInfo(); 	    	
    	crsc.getChangeHistory().updateModel();
    	
    	 rru = RelationRetrivalUtil.getInstance(si);
	}
	
	public Map<String,Metrics> extractMetrics() throws IOException, ParserException{
		init();
		
		ForestWalker fw = new ForestWalker(cul);
    	while(fw.next()){
    		ProgramElement pe = fw.getProgramElement();
    		if(pe instanceof ClassDeclaration){
    			
    			//exclude 
    			if(((ClassDeclaration) pe).getName()== null){
    				continue;
    			}
    			if(((ClassDeclaration) pe).getName().contains("FileWriter")){
    				System.out.println(((ClassDeclaration) pe).getSupertypes());
    			}
    			
    			Metrics m = new Metrics();
    			String name = ((ClassDeclaration) pe).getFullName();
    			
    			if(((ClassDeclaration) pe).isAbstract()){
    				m.setAbstract(true);
    			}
    			
    			m.setFullname(name);
    			
    			m.setNof(((ClassDeclaration) pe).getFields().size());
    			m.setNsf(countStaticFields(((ClassDeclaration)pe).getFields()));
    			
    			m.setNom(((ClassDeclaration) pe).getMethods().size());
    			m.setNsm(countStaticMethods(((ClassDeclaration)pe).getMethods()));
    			m.setNoam(countAbstractMethods(((ClassDeclaration) pe).getMethods()));
    			m.setNorm(countOverridenMethods(((ClassDeclaration) pe).getExtendedTypes(),((ClassDeclaration) pe).getMethods()));
    			m.setNopm(countPublicMethods(((ClassDeclaration) pe).getMethods()));
    			//exclude object
    			m.setNoi(rru.getAllSuperTypesWithoutObject(((ClassDeclaration) pe)).size());
    			
    			
    			m.setNoof(countObjectFields(((ClassDeclaration) pe).getFields()));
    			m.setNotc(countConstructWithObjectType(((ClassDeclaration) pe).getConstructors()));
    		
    			metricsMap.put(name, m);	
    		}else if(pe instanceof InterfaceDeclaration){
    			
    			Metrics m = new Metrics();
    			String name = ((InterfaceDeclaration) pe).getFullName();
    			m.setFullname(name);
    			m.setNof(((InterfaceDeclaration) pe).getFields().size());
    			m.setNsf(countStaticFields(((InterfaceDeclaration) pe).getFields()));
    			m.setNom(((InterfaceDeclaration) pe).getMethods().size());
    			m.setNsm(countStaticMethods(((InterfaceDeclaration) pe).getMethods()));
    			m.setNoi(rru.getAllSuperTypesWithoutObject(((InterfaceDeclaration) pe)).size());
    			m.setNoam(countAbstractMethods(((InterfaceDeclaration) pe).getMethods()));
    			m.setNorm(0);
    			m.setNoof(countObjectFields(((InterfaceDeclaration) pe).getFields()));
    			
    			metricsMap.put(name, m);	
    		}
    	}
    	walkAgain();
    	return metricsMap;
	}
	


	private void walkAgain(){
		//walk the tree again to decide
		
		ForestWalker fw = new ForestWalker(cul);
    	while(fw.next()){
    		ProgramElement pe = fw.getProgramElement();
    		if(pe instanceof ClassDeclaration){

    			
    			//check fields to decide // number of other classes with field of own type
    			for(FieldSpecification f: ((ClassDeclaration) pe).getFields()){
    				Type t = si.getType(f);
    				if(t instanceof ClassDeclaration || t instanceof InterfaceDeclaration){
    					//System.out.println(((ClassDeclaration) pe).getName()+"  contains  " + t.getName() +"\n");
    					//t owned by pe
    					//find pe from map and increase the NCOF of t by 1
    					Metrics  m = metricsMap.get(t.getFullName());
        				if(m!=null){
        					m.addNcof();
        				}
    				}
    			}	
    			
    			//construct relationship between delegator & delegatee
    			List<ClassType> ctl = rru.getDelegateeClasses(((ClassDeclaration)pe));
    			for(ClassType ct : ctl){
    				Metrics mDelegatee = metricsMap.get(ct.getFullName());
    				if(mDelegatee!=null){
    					Metrics mDelegator = metricsMap.get(((ClassDeclaration) pe).getFullName());
    					if(mDelegator!=null){
    						mDelegator.addNodr();
        					mDelegatee.addNode();
    					}  					
    				}
    			}
    			
    		}
    		
    		if(pe instanceof ClassDeclaration || pe instanceof InterfaceDeclaration){
    			for(ClassType ct :((TypeDeclaration)pe).getSupertypes()){
    				Metrics  m = metricsMap.get(ct.getFullName());
    				if(m!=null){
    					m.addNos();
    				}
    			}
    		}
    	}
	}
	
	private  int countObjectFields(List<FieldSpecification> fields) {
		int num = 0;
		for(FieldSpecification f : fields){
			Type t = f.getType();
			if(t instanceof ClassDeclaration || t instanceof InterfaceDeclaration){
				num ++;
			}
		}
		return num;
	}

	private Set<String> getOverriddenMethod(Extends extend, List<Method> methods,Set<String> overriddenMethods){
		//recursion
		
		if(extend == null){
			return overriddenMethods;
		}
		
		TypeReference tr = (TypeReference) extend.getChildAt(0);
		
		if(si.getType(tr) instanceof ClassType){
			ClassType superType = (ClassType) si.getType(tr);
			
			RelationJudgeUtil rju = RelationJudgeUtil.getInstance(si);
			for(Method m : methods){
				// check whether m is an overridden method
				if(rju.isOverrideMethod(superType, m)){
					overriddenMethods.add(m.getName());
				}
			}	
			if(superType instanceof ClassDeclaration ){
				if(((ClassDeclaration) superType).getExtendedTypes()!=null){
					
					getOverriddenMethod(((ClassDeclaration) superType).getExtendedTypes(),methods,overriddenMethods);
				}
				
			}
			
		}
		return overriddenMethods;
	}
	private  int countOverridenMethods( Extends extend, List<Method> methods) {
		
		if(extend == null){
			return 0;
		}
		
		Set<String> overriddenMethods = new HashSet<String>();
		getOverriddenMethod(extend,methods,overriddenMethods);
		
		return overriddenMethods.size();
	}

	private static int countAbstractMethods(List<Method> methods) {
		int num = 0;
		for(Method m : methods){
			if(m.isAbstract()) num++;
		}
		return num;
	}

	private  int countInterfaces(Implements i) {
		
		if(i!=null){
			return i.getChildCount();
		}
		
		return 0;
		
	}

	private  int countStaticMethods(List<Method> mList) {
		int num =0;
		for(Method m: mList){
			if(m.isStatic()){
				num += 1;
			}
		}
		return num;
	}

	private  int countStaticFields(List<FieldSpecification> fList) {
		int num = 0;
		for(FieldSpecification fd : fList){
			if(fd.isStatic()){
				num+=1;
			}
		}
		return 0;
	}
	
	
	
	private int countConstructWithObjectType(
			List<? extends Constructor> constructors) {
		// number of constructors with argument of object type
		// notc
		int num = 0;
		if(constructors!=null){
			for(Constructor ctor : constructors){
				//signature is want i want
				boolean hasObjectType = false;
				List<Type> paramList = ctor.getSignature();
				for(Type type : paramList){
					if(type instanceof ClassType){
						//interfaceDeclaration & ClassDeclaration are
						//both included in classtype
						//System.out.println(type.getName());
						hasObjectType = true;
					}
				}
				if(hasObjectType){
					num++;
				}
			}
		}
		
		return num;
	}
	
	private int countPublicMethods(List<Method> methods) {
		int num=0;
		for(Method m: methods){
			if(m.isPublic()){
				num++;
			}
		}
		return num;
	}
	
	public Map<String,Metrics> getMetricsMap(){
		return metricsMap;
	}
}
